package com.itgacl.magic4j.libcommon.component.license.service.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.db.Db;
import cn.hutool.db.Entity;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.itgacl.magic4j.libcommon.component.license.LicenseConstants;
import com.itgacl.magic4j.libcommon.component.license.LicenseSequences;
import com.itgacl.magic4j.libcommon.component.license.bean.LicenseInfo;
import com.itgacl.magic4j.libcommon.component.license.crypto.B64Utils;
import com.itgacl.magic4j.libcommon.component.license.crypto.RSAUtils;
import com.itgacl.magic4j.libcommon.component.license.dto.LicenseCheckDTO;
import com.itgacl.magic4j.libcommon.component.license.dto.LicenseDTO;
import com.itgacl.magic4j.libcommon.component.license.service.LicenseService;
import com.itgacl.magic4j.libcommon.component.license.vo.LicenseCheckResultVo;
import com.itgacl.magic4j.libcommon.component.license.vo.LicenseVo;
import com.itgacl.magic4j.libcommon.exception.Magic4jException;
import com.itgacl.magic4j.libcommon.util.DateUtils;
import com.itgacl.magic4j.libcommon.util.SpringContextUtils;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.sql.DataSource;
import java.io.*;
import java.security.Key;
import java.util.Map;
import java.util.StringTokenizer;

/**
 * @author gacl
 * @version 1.0
 * @description: License校验service
 * @date 2021/12/24 16:07
 */
@Slf4j
@Service
public class LicenseServiceImpl implements LicenseService {

    @Value("${magic4j.license.checkApiHost}")
    private String checkApiHost;

    @Value("${magic4j.license.licFilePath}")
    private String licFilePath;

    /**
     * 解析License文件
     * @param file
     * @return
     * @throws Exception
     */
    @Override
    public LicenseVo decode(MultipartFile file) throws Exception {
        String[] licenseContentArr = readLicense(file);
        if(licenseContentArr==null){
            throw new Magic4jException("解析证书内容出错");
        }
        LicenseVo licenseVo = new LicenseVo();
        String pubKeyStr = licenseContentArr[0];
        byte[] pubKeyBytes = B64Utils.decode(pubKeyStr);
        byte[] decryptBytes = RSAUtils.decryptPublic(pubKeyBytes, B64Utils.decode(licenseContentArr[1]));
        String[] decodeContentArr = decodeString(decryptBytes);
        licenseVo.setPublicKey(pubKeyStr);
        licenseVo.setMode(Integer.parseInt(decodeContentArr[0]));
        licenseVo.setMachineCode(decodeContentArr[1]);
        licenseVo.setExpiryTime(decodeContentArr[2]);
        return licenseVo;
    }

    /**
     * 创建license
     * @param licenseDTO
     * @return
     */
    @Override
    public LicenseInfo create(LicenseDTO licenseDTO) throws Exception {
        LicenseInfo licenseInfo = null;
        String expiryTime = DateUtils.formatDate(licenseDTO.getExpiryTime(), "yyyyMMddHHmmss");
        String seed = RandomUtil.randomString(16);//生成随机16位私钥种子
        licenseDTO.setSeed(seed);
        if (licenseDTO.getMode() == LicenseConstants.MODE_SINGLE) {
            licenseInfo = generateSingle(licenseDTO.getLicenseIp(), licenseDTO.getSeed());
        } else if (licenseDTO.getMode() == LicenseConstants.MODE_INTIME) {
            licenseInfo = generateInTime(licenseDTO.getLicenseIp(), licenseDTO.getSeed(), expiryTime);
        } else if (licenseDTO.getMode() == LicenseConstants.MODE_ONLINE) {
            licenseInfo = generateOnline(licenseDTO.getLicenseIp(), licenseDTO.getSeed(), expiryTime);
        }

        return licenseInfo;
    }

    /**
     * 校验License，校验不通过将停止应用
     */
    @Override
    public void validateLicense() {
        //File file = new File(LicenseConstants.LICENSE_ROOT_PATH, LicenseConstants.licenseFileName);
        File file = new File(licFilePath);
        log.info("校验License，使用的license文件路径：{}",file.getAbsolutePath());
        String[] txt = readLicense(file);//读取license文件
        if(txt==null){
            log.error("License文件无效或文件错误，错误代码：" + LicenseConstants.STATE_FAILD);
            System.exit(0);//主动停止应用
        }
        byte[] licenseKeys = B64Utils.decode(txt[0]);//用户RSA公钥
        byte[] licenseContent = B64Utils.decode(txt[1]);// License内容
        validateLicense(licenseKeys, licenseContent);
    }

    /**
     * 校验License，返回校验状态
     * @return
     */
    @Override
    public int licenseCheck(){
        //File file = new File(LicenseConstants.LICENSE_ROOT_PATH, LicenseConstants.licenseFileName);
        File file = new File(licFilePath);
        log.info("校验License，使用的license文件路径：{}",file.getAbsolutePath());
        String[] txt = readLicense(file);//读取license文件
        if(txt==null){
            log.error("License文件错误，错误代码：" + LicenseConstants.STATE_FAILD);
            return LicenseConstants.STATE_FAILD;
        }
        byte[] licenseKeys = B64Utils.decode(txt[0]);//用户RSA公钥
        byte[] licenseContent = B64Utils.decode(txt[1]);// License内容
        return licenseCheck(licenseKeys, licenseContent);
    }

    @Override
    public LicenseCheckResultVo licenseCheck(LicenseCheckDTO licenseDTO) {
        LicenseCheckResultVo licenseCheckResultVo = new LicenseCheckResultVo();
        DataSource dataSource = SpringContextUtils.getBean(DataSource.class);
        try {
            Entity entity = Db.use(dataSource).queryOne("SELECT * FROM comm_license WHERE license_code=? AND delete_flag=0", licenseDTO.getLicenseCode());
            if (entity == null) {
                licenseCheckResultVo.setCode(LicenseConstants.STATUS_CODE.INVALID);
                licenseCheckResultVo.setMessage("授权码无效");
            } else {
                Integer status = entity.getInt("status");
                if (status == LicenseConstants.STATUS_CODE.NORMAL) {
                    licenseCheckResultVo.setCode(LicenseConstants.STATUS_CODE.NORMAL);
                    licenseCheckResultVo.setMessage("授权校验通过");
                } else if (status == LicenseConstants.STATUS_CODE.FROZEN) {
                    licenseCheckResultVo.setCode(LicenseConstants.STATUS_CODE.FROZEN);
                    licenseCheckResultVo.setMessage("授权已被禁用");
                } else {
                    licenseCheckResultVo.setCode(LicenseConstants.STATUS_CODE.EXPIRE);
                    licenseCheckResultVo.setMessage("授权已过期");
                }
            }
        } catch (Exception e) {
            licenseCheckResultVo.setCode(LicenseConstants.STATUS_CODE.ERROR);
            licenseCheckResultVo.setMessage("授权校验出错");
            log.error(e.getMessage(), e);
        }
        return licenseCheckResultVo;
    }

    /**
     * 校验License
     */
    private int licenseCheck(byte[] licenseKeys,byte[] licenseContent) {
        int licenseState = LicenseConstants.STATE_SUCCESS;
        String[] dTxt = null;
        try {
            dTxt = decodeString(RSAUtils.decryptPublic(licenseKeys, licenseContent));
        } catch (Exception e) {
            licenseState = LicenseConstants.STATE_FAILD;
        }
        if(dTxt==null){
            log.error("License无效或文件错误，错误代码：" + licenseState);
            return licenseState;
        }
        int mode = Integer.parseInt(dTxt[LicenseConstants.CONTENT_MODE]);
        switch (mode) {//验证模式
            case LicenseConstants.MODE_SINGLE:
                licenseState = validateSingle(dTxt[LicenseConstants.CONTENT_CODE]);
                break;
            case LicenseConstants.MODE_INTIME:
                licenseState = validateInTime(dTxt[LicenseConstants.CONTENT_TIME]);
                break;
            case LicenseConstants.MODE_ONLINE:
                licenseState = onlineValidate(dTxt);
                break;
        }
        return licenseState;
    }

    /**
     * 校验License
     */
    private void validateLicense(byte[] licenseKeys,byte[] licenseContent) {
        int licenseState = LicenseConstants.STATE_SUCCESS;
        String[] dTxt = null;
        try {
            dTxt = decodeString(RSAUtils.decryptPublic(licenseKeys, licenseContent));
        } catch (Exception e) {
            licenseState = LicenseConstants.STATE_FAILD;
        }
        if(dTxt==null){
            log.error("License无效或文件错误，错误代码：" + licenseState);
            System.exit(0);
        }
        int mode = Integer.parseInt(dTxt[LicenseConstants.CONTENT_MODE]);
        switch (mode) {//验证模式
            case LicenseConstants.MODE_SINGLE:
                licenseState = validateSingle(dTxt[LicenseConstants.CONTENT_CODE]);
                break;
            case LicenseConstants.MODE_INTIME:
                licenseState = validateInTime(dTxt[LicenseConstants.CONTENT_TIME]);
                break;
            case LicenseConstants.MODE_ONLINE:
                licenseState = onlineValidate(dTxt);
                break;
        }
        switch (licenseState) {
            case LicenseConstants.STATE_FAILD:
                log.error("License无效或文件错误，错误代码：" + LicenseConstants.STATE_FAILD);
                System.exit(0);//主动停止应用
            case LicenseConstants.STATE_TIMEOUT:
                log.error("用户License已经过期，将停止服务器运行，错误代码：" + LicenseConstants.STATE_TIMEOUT);
                System.exit(0);//主动停止应用
                break;
            case LicenseConstants.STATE_CODE_ERR:
                log.error("用户License信息无效，将停止服务器运行，错误代码：" + LicenseConstants.STATE_CODE_ERR);
                System.exit(0);//主动停止应用
                break;
            case LicenseConstants.STATE_SUCCESS:
                log.info("License有效，服务器可正常运行。");
                break;
        }
    }

    /**
     * 单机部署验证
     */
    private int validateSingle(String machineCode) {
        if (machineCode.equals(LicenseSequences.sequence())) {//验证机器码
            return LicenseConstants.STATE_SUCCESS;
        } else {
            return LicenseConstants.STATE_CODE_ERR;
        }
    }

    /**
     * 授权有效期验证
     * @param expTime
     */
    private int validateInTime(String expTime){
        long exptime = convert2long(expTime);
        long now_time = DateTime.now().getMillis() / 1000;
        if (now_time > exptime) {
            return LicenseConstants.STATE_TIMEOUT;
        } else {
            return LicenseConstants.STATE_SUCCESS;
        }
    }

    private int onlineValidate(String[] licenseContent) {
        LicenseCheckDTO licenseCheckDTO = new LicenseCheckDTO();
        licenseCheckDTO.setMode(Integer.parseInt(licenseContent[0]));
        licenseCheckDTO.setLicenseCode(licenseContent[1]);
        licenseCheckDTO.setExpiryTime(DateUtils.parseDate(licenseContent[2]));

        String apiUrl = checkApiHost + "/api/license/check";
        String retVal = HttpUtil.createPost(apiUrl)
                .header("Content-Type", "application/json")
                .body(JSON.toJSONString(licenseCheckDTO))
                .execute()
                .body();//请求远程License服务器进行校验
        log.debug("onlineValidate retVal:{}", retVal);
        JSONObject jsonObject = JSON.parseObject(retVal);
        LicenseCheckResultVo checkResultVo = jsonObject.getObject("data", LicenseCheckResultVo.class);
        if (checkResultVo.getCode() == LicenseConstants.STATUS_CODE.NORMAL) {
            return LicenseConstants.STATE_SUCCESS;
        }
        if (checkResultVo.getCode() == LicenseConstants.STATUS_CODE.FROZEN) {
            return LicenseConstants.STATE_CODE_FROZEN;
        }
        if (checkResultVo.getCode() == LicenseConstants.STATUS_CODE.EXPIRE) {
            return LicenseConstants.STATE_TIMEOUT;
        }
        return LicenseConstants.STATE_CODE_ERR;
    }

    /**
     * 编码密文内容：模式:机器码(xxxx-xxxx-xxxxxxxx-xxxxxxxxxxxxxxxx):服务日期(yyyyMMddHHmmss)
     *
     * @param mode 模式
     * @param code 机器码
     * @param days 服务日期
     * @return
     */
    private static String encodeString(int mode, String code, String days) {
        return mode + ":" + code + ":" + days;
    }

    /**
     * 解码密文内容
     *
     * @param data 字符串二进制数组
     * @return 密文字符串数组
     */
    private static String[] decodeString(byte[] data) {
        StringTokenizer st = new StringTokenizer(new String(data), ":");
        String[] str = new String[3];
        int idx = 0;
        while (st.hasMoreTokens())
            str[idx++] = st.nextToken();
        return str;
    }

    /**
     * 生成License，并写入文件
     *
     * @param machineCode 机器码
     * @param seed 种子
     * @param mode 模式
     * @param time 可用时长
     * @throws Exception 错误
     */
    private LicenseInfo generate(String machineCode, String seed, int mode, String time) throws Exception {
        String sequence = LicenseSequences.sequence(machineCode);
        LicenseInfo licenseInfo = new LicenseInfo();
        licenseInfo.setSeed(seed);
        licenseInfo.setExpiryTime(time);
        licenseInfo.setMode(mode);
        licenseInfo.setMachineCode(sequence);
        Map<String, Key> keyMap = RSAUtils.genkeysMap(seed);
        byte[] privateKeyByte = RSAUtils.privateKeyByte(keyMap);
        byte[] publicKeyByte = RSAUtils.publicKeyByte(keyMap);
        String priKeyStr = B64Utils.encode(privateKeyByte);
        String pubKeyStr = B64Utils.encode(publicKeyByte);
        licenseInfo.setLicensePrivateKey(priKeyStr);
        licenseInfo.setLicensePublicKey(pubKeyStr);

        String str = encodeString(mode, sequence, time);
        byte[] bytes = RSAUtils.encryptPrivate(privateKeyByte, str.getBytes());
        String licenseContent = B64Utils.encode(bytes);
        licenseInfo.setLicenseContent(licenseContent);
        //String licenseFilePath = writeLicense(pubKeyStr, licenseContent);
        String licenseFileName = sequence+"_"+mode+"_"+LicenseConstants.licenseFileName;
        String licenseFilePath = writeLicense(pubKeyStr, licenseContent,licenseFileName);
        licenseInfo.setLicenseFilePath(licenseFilePath);
        return licenseInfo;
    }

    /**
     * 创建单机模式License
     *
     * @param machineCode 机器码
     * @param seed 私钥种子
     * @throws Exception 错误
     */
    private LicenseInfo generateSingle(String machineCode,String seed) throws Exception {
        return generate(machineCode,seed, LicenseConstants.MODE_SINGLE,"");
    }

    /**
     * 创建时长模式License
     *
     * @param machineCode 机器码
     * @param seed 私钥种子
     * @param time 有效期(格式：yyyyMMddHHmmss)
     * @throws Exception 错误
     */
    private LicenseInfo generateInTime(String machineCode,String seed, String time) throws Exception {
        return generate(machineCode,seed, LicenseConstants.MODE_INTIME, time);
    }

    /**
     * 创建联网模式License
     *
     * @param machineCode 机器码
     * @param seed 私钥种子
     * @param time 有效期(格式：yyyyMMddHHmmss)
     * @throws Exception 错误
     */
    private LicenseInfo generateOnline(String machineCode,String seed, String time) throws Exception {
        return generate(machineCode,seed, LicenseConstants.MODE_ONLINE, time);
    }

    /**
     * 将信息写入文件
     *
     * @param keys 用户公钥
     * @param data 加密内容
     * @return licenseFilePath license文件的保存路径
     */
    private String writeLicense(String keys, String data) {
        String licenseFilePath = null;
        try {
            File licenseRootPath = new File(LicenseConstants.LICENSE_ROOT_PATH);
            if (!licenseRootPath.exists()) {
                licenseRootPath.mkdirs();
            }
            File file = new File(LicenseConstants.LICENSE_ROOT_PATH, LicenseConstants.licenseFileName);
            if (file.exists())
                file.delete();

            FileWriter fw = new FileWriter(file);
            BufferedWriter bw = new BufferedWriter(fw);
            bw.write(keys);
            bw.newLine();
            bw.write(data);

            bw.close();
            fw.close();

            licenseFilePath = file.getAbsolutePath();
        } catch (Exception e) {
            log.error("License文件生成失败，失败原因：{}",e.getMessage());
        }
        return licenseFilePath;
    }

    /**
     * 将信息写入文件
     *
     * @param keys 用户公钥
     * @param data 加密内容
     * @return licenseFilePath license文件的保存路径
     */
    private String writeLicense(String keys, String data,String licenseFileName) {
        String licenseFilePath = null;
        try {
            File licenseRootPath = new File(LicenseConstants.LICENSE_ROOT_PATH);
            if (!licenseRootPath.exists()) {
                licenseRootPath.mkdirs();
            }
            File file = new File(LicenseConstants.LICENSE_ROOT_PATH, licenseFileName);
            if (file.exists())
                file.delete();

            FileWriter fw = new FileWriter(file);
            BufferedWriter bw = new BufferedWriter(fw);
            bw.write(keys);
            bw.newLine();
            bw.write(data);

            bw.close();
            fw.close();

            licenseFilePath = file.getAbsolutePath();
        } catch (Exception e) {
            log.error("License文件生成失败，失败原因：{}",e.getMessage());
        }
        return licenseFilePath;
    }

    /**
     * 读取License文件信息
     *
     * @param file License文件
     * @return [2]-0:Key 1:Txt
     */
    private String[] readLicense(File file) {
        try {
            String[] txt = new String[2];
            FileReader fr = new FileReader(file);
            BufferedReader br = new BufferedReader(fr);

            txt[0] = br.readLine();
            txt[1] = br.readLine();

            br.close();
            fr.close();

            return txt;
        } catch (Exception e) {
            log.error("License文件读取失败：" + e.getMessage());
        }
        return null;
    }

    /**
     * 读取License文件信息
     *
     * @param file License文件
     * @return [2]-0:Key 1:Txt
     */
    private String[] readLicense(MultipartFile file) {
        try {
            String[] txt = new String[2];
            InputStream inputStream = file.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

            txt[0] = bufferedReader.readLine();
            txt[1] = bufferedReader.readLine();

            bufferedReader.close();
            inputStreamReader.close();

            return txt;
        } catch (Exception e) {
            log.error("License文件读取失败：" + e.getMessage());
        }
        return null;
    }

    /**
     * 字符串日期转成11位long型
     *
     * @param date   日期
     * @return
     */
    private static long convert2long(String date) {
        try {
            DateTime dateTime = DateTime.parse(date, DateTimeFormat.forPattern("yyyyMMddHHmmss"));
            return dateTime.getMillis() / 1000;
        } catch (Exception e) {
            log.error("DateUtils.convert2long exception:", e);
        }
        return 0L;
    }
}
